/**
 * @file usb_com.cpp
 * @author Jae Frank (thissfk@qq.com)
 * @brief 
 * @version 0.1
 * @date 2024-01-17
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "usb_com.hpp"

//-----------------------------------------------------------------------------
#ifdef CE_WINDOWS
#define READ_TIMEOUT 10 // milliseconds
#else
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
#endif

/**
 * @brief 延时函数
 *
 * @param ms 毫秒
 */
void ceSerial::Delay(unsigned long ms)
{
#ifdef CE_WINDOWS
    Sleep(ms);
#else
    usleep(ms * 1000);
#endif
}

/**
 * @brief 生成端口号码字符串
 *
 * @param com_num
 * @return std::string
 */
std::string ceSerial::genComNumber(uint8_t com_num)
{
#ifdef CE_WINDOWS
    return "\\\\.\\COM" + std::to_string(com_num);

#else
    return "/dev/ttyS" + std::to_string(com_num);
#endif
}

/**
 * @brief Construct a new ce Serial::ce Serial object默认构造函数
 *
 */
ceSerial::ceSerial() :
#ifdef CE_WINDOWS
                       ceSerial("\\\\.\\COM1", 9600, 8, 'N', 1)
#else
                       ceSerial("/dev/ttyS0", 9600, 8, 'N', 1)
#endif
{
}

/**
 * @brief Construct a new ce Serial::ce Serial object参数构造函数
 *
 * @param Device 端口号描述字符串
 * @param BaudRate 波特率
 * @param DataSize 数据位长度
 * @param ParityType 校验类型
 * @param NStopBits 停止位长度
 */
ceSerial::ceSerial(std::string Device, long BaudRate, long DataSize, char ParityType, float NStopBits) : stdbaud(true)
{
#ifdef CE_WINDOWS
    hComm = INVALID_HANDLE_VALUE;
#else
    fd = -1;
#endif // defined
    SetBaudRate(BaudRate);
    SetDataSize(DataSize);
    SetParity(ParityType);
    SetStopBits(NStopBits);
    SetPortName(Device);
}

/**
 * @brief Destroy the ce Serial::ce Serial object析构函数
 * 
 */
ceSerial::~ceSerial()
{
    Close();
}

/**
 * @brief 设置端口号描述字符串函数
 * 
 * @param Device 
 */
void ceSerial::SetPortName(std::string Device)
{
    port = Device;
}

/**
 * @brief 获取端口描述字符串
 * 
 * @return std::string 
 */
std::string ceSerial::GetPort()
{
    return port;
}

/**
 * @brief 设置数据位长度
 * 
 * @param nbits 数据位长度
 */
void ceSerial::SetDataSize(long nbits)
{
    if ((nbits < 5) || (nbits > 8))
        nbits = 8;
    dsize = nbits;
}

/**
 * @brief 获取数据位长度
 * 
 * @return long 数据位长度
 */
long ceSerial::GetDataSize()
{
    return dsize;
}

/**
 * @brief 设置校验位类型
 * 
 * @param p 
 */
void ceSerial::SetParity(char p)
{
    if ((p != 'N') && (p != 'E') && (p != 'O'))
    {
#ifdef CE_WINDOWS
        if ((p != 'M') && (p != 'S'))
            p = 'N';
#else
        p = 'N';
#endif
    }
    parity = p;
}

/**
 * @brief 获取校验位类型
 * 
 * @return char 
 */
char ceSerial::GetParity()
{
    return parity;
}

/**
 * @brief 设置停止位类型
 * 
 * @param nbits 
 */
void ceSerial::SetStopBits(float nbits)
{
    if (nbits >= 2)
        stopbits = 2;
#ifdef CE_WINDOWS
    else if (nbits >= 1.5)
        stopbits = 1.5;
#endif
    else
        stopbits = 1;
}

/**
 * @brief 获取停止位类型
 * 
 * @return float 
 */
float ceSerial::GetStopBits()
{
    return stopbits;
}


#ifdef CE_WINDOWS
/**
 * @brief 设置波特率
 * 
 * @param baudrate 
 */
void ceSerial::SetBaudRate(long baudrate)
{
    stdbaud = true;
    if (baudrate == 1100)
        baud = CBR_110;
    else if (baudrate == 300)
        baud = CBR_300;
    else if (baudrate == 600)
        baud = CBR_600;
    else if (baudrate == 1200)
        baud = CBR_1200;
    else if (baudrate == 2400)
        baud = CBR_2400;
    else if (baudrate == 4800)
        baud = CBR_4800;
    else if (baudrate == 9600)
        baud = CBR_9600;
    else if (baudrate == 14400)
        baud = CBR_14400;
    else if (baudrate == 19200)
        baud = CBR_19200;
    else if (baudrate == 38400)
        baud = CBR_38400;
    else if (baudrate == 57600)
        baud = CBR_57600;
    else if (baudrate == 115200)
        baud = CBR_115200;
    else if (baudrate == 128000)
        baud = CBR_128000;
    else if (baudrate == 256000)
        baud = CBR_256000;
    else
    {
        baud = baudrate;
        stdbaud = false;
    }
}


/**
 * @brief 获取波特率
 * 
 * @return long 所设置的波特率
 */
long ceSerial::GetBaudRate()
{
    return baud;
}

/**
 * @brief 打开端口
 * 
 * @return long 0:成功 -1:失败
 */
long ceSerial::Open()
{
    if (IsOpened())
        return 0;
#ifdef UNICODE
    wstring wtext(port.begin(), port.end());
#else
    std::string wtext = port;
#endif
    hComm = CreateFile(wtext.c_str(),
                       GENERIC_READ | GENERIC_WRITE,
                       0,
                       0,
                       OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                       0);
    if (hComm == INVALID_HANDLE_VALUE)
    {
        return -1;
    }

    if (PurgeComm(hComm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR) == 0)
    {
        return -1;
    } // purge

    // get initial state
    DCB dcbOri;
    bool fSuccess;
    fSuccess = GetCommState(hComm, &dcbOri);
    if (!fSuccess)
    {
        return -1;
    }

    DCB dcb1 = dcbOri;

    dcb1.BaudRate = baud;

    if (parity == 'E')
        dcb1.Parity = EVENPARITY;
    else if (parity == 'O')
        dcb1.Parity = ODDPARITY;
    else if (parity == 'M')
        dcb1.Parity = MARKPARITY;
    else if (parity == 'S')
        dcb1.Parity = SPACEPARITY;
    else
        dcb1.Parity = NOPARITY;

    dcb1.ByteSize = (BYTE)dsize;

    if (stopbits == 2)
        dcb1.StopBits = TWOSTOPBITS;
    else if (stopbits == 1.5)
        dcb1.StopBits = ONE5STOPBITS;
    else
        dcb1.StopBits = ONESTOPBIT;

    dcb1.fOutxCtsFlow = false;
    dcb1.fOutxDsrFlow = false;
    dcb1.fOutX = false;
    dcb1.fDtrControl = DTR_CONTROL_DISABLE;
    dcb1.fRtsControl = RTS_CONTROL_DISABLE;
    fSuccess = SetCommState(hComm, &dcb1);
    this->Delay(60);
    if (!fSuccess)
    {
        return -1;
    }

    fSuccess = GetCommState(hComm, &dcb1);
    if (!fSuccess)
    {
        return -1;
    }

    osReader = {0}; // Create the overlapped event.
    // Must be closed before exiting to avoid a handle leak.
    osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (osReader.hEvent == NULL)
    {
        return -1;
    } // Error creating overlapped event; abort.
    fWaitingOnRead = FALSE;

    osWrite = {0};
    osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (osWrite.hEvent == NULL)
    {
        return -1;
    }

    if (!GetCommTimeouts(hComm, &timeouts_ori))
    {
        return -1;
    } // Error getting time-outs.
    COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout = 20;
    timeouts.ReadTotalTimeoutMultiplier = 15;
    timeouts.ReadTotalTimeoutConstant = 100;
    timeouts.WriteTotalTimeoutMultiplier = 15;
    timeouts.WriteTotalTimeoutConstant = 100;
    if (!SetCommTimeouts(hComm, &timeouts))
    {
        return -1;
    } // Error setting time-outs.
    return 0;
}

/**
 * @brief 关闭端口
 * 
 */
void ceSerial::Close()
{
    if (IsOpened())
    {
        SetCommTimeouts(hComm, &timeouts_ori);
        CloseHandle(osReader.hEvent);
        CloseHandle(osWrite.hEvent);
        CloseHandle(hComm); // close comm port
        hComm = INVALID_HANDLE_VALUE;
    }
}

/**
 * @brief 获取端口打开情况
 * 
 * @return true 已经打开
 * @return false 没有打开
 */
bool ceSerial::IsOpened()
{
    if (hComm == INVALID_HANDLE_VALUE)
        return false;
    else
        return true;
}

/**
 * @brief 串口发送函数
 * 
 * @param data 所要发送的数据
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::Write(const char data[])
{
    if (!IsOpened())
    {
        return false;
    }
    BOOL fRes;
    DWORD dwWritten;
    long n = strlen(data);
    if (n < 0)
        n = 0;
    else if (n > 1024)
        n = 1024;

    // Issue write.
    if (!WriteFile(hComm, data, n, &dwWritten, &osWrite))
    {
        // WriteFile failed, but it isn't delayed. Report error and abort.
        if (GetLastError() != ERROR_IO_PENDING)
        {
            fRes = FALSE;
        }
        else
        { // Write is pending.
            if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, TRUE))
                fRes = FALSE;
            else
                fRes = TRUE; // Write operation completed successfully.
        }
    }
    else
        fRes = TRUE; // WriteFile completed immediately.
    return fRes;
}

/**
 * @brief 串口发送函数
 * 
 * @param data 发送的数据
 * @param n 发送的字节数
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::Write(char *data, long n)
{
    if (!IsOpened())
    {
        return false;
    }
    BOOL fRes;
    DWORD dwWritten;
    if (n < 0)
        n = 0;
    else if (n > 1024)
        n = 1024;

    // Issue write.
    if (!WriteFile(hComm, data, n, &dwWritten, &osWrite))
    {
        // WriteFile failed, but it isn't delayed. Report error and abort.
        if (GetLastError() != ERROR_IO_PENDING)
        {
            fRes = FALSE;
        }
        else
        { // Write is pending.
            if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, TRUE))
                fRes = FALSE;
            else
                fRes = TRUE; // Write operation completed successfully.
        }
    }
    else
        fRes = TRUE; // WriteFile completed immediately.
    return fRes;
}

/**
 * @brief 发送一个字节
 * 
 * @param ch 要发送的字节
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::WriteChar(char ch)
{
    char s[2];
    s[0] = ch;
    s[1] = 0; // null terminated
    return Write(s);
}

/**
 * @brief 接收一个字节
 * 
 * @param success 接受成功标志
 * @return char 接收到的字节
 */
char ceSerial::ReadChar(bool &success)
{
    success = false;
    if (!IsOpened())
    {
        return 0;
    }

    DWORD dwRead;
    DWORD length = 1;
    BYTE *data = (BYTE *)(&rxchar);
    // the creation of the overlapped read operation
    if (!fWaitingOnRead)
    {
        // Issue read operation.
        if (!ReadFile(hComm, data, length, &dwRead, &osReader))
        {
            if (GetLastError() != ERROR_IO_PENDING)
            { /*Error*/
            }
            else
            {
                fWaitingOnRead = TRUE; /*Waiting*/
            }
        }
        else
        {
            if (dwRead == length)
                success = true;
        } // success
    }

    // detection of the completion of an overlapped read operation
    DWORD dwRes;
    if (fWaitingOnRead)
    {
        dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
        switch (dwRes)
        {
        // Read completed.
        case WAIT_OBJECT_0:
            if (!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))
            { /*Error*/
            }
            else
            {
                if (dwRead == length)
                    success = true;
                fWaitingOnRead = FALSE;
                // Reset flag so that another opertion can be issued.
            } // Read completed successfully.
            break;

        case WAIT_TIMEOUT:
            // Operation isn't complete yet.
            break;

        default:
            // Error in the WaitForSingleObject;
            break;
        }
    }
    return rxchar;
}

/**
 * @brief 设置RTS
 * 
 * @param value 
 * @return true 
 * @return false 
 */
bool ceSerial::SetRTS(bool value)
{
    bool r = false;
    if (IsOpened())
    {
        if (value)
        {
            if (EscapeCommFunction(hComm, SETRTS))
                r = true;
        }
        else
        {
            if (EscapeCommFunction(hComm, CLRRTS))
                r = true;
        }
    }
    return r;
}

/**
 * @brief 设置DTR
 * 
 * @param value 
 * @return true 
 * @return false 
 */
bool ceSerial::SetDTR(bool value)
{
    bool r = false;
    if (IsOpened())
    {
        if (value)
        {
            if (EscapeCommFunction(hComm, SETDTR))
                r = true;
        }
        else
        {
            if (EscapeCommFunction(hComm, CLRDTR))
                r = true;
        }
    }
    return r;
}

/**
 * @brief 获取CTS
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetCTS(bool &success)
{
    success = false;
    bool r = false;
    if (IsOpened())
    {
        DWORD dwModemStatus;
        if (GetCommModemStatus(hComm, &dwModemStatus))
        {
            r = MS_CTS_ON & dwModemStatus;
            success = true;
        }
    }
    return r;
}

/**
 * @brief 获取DSR
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetDSR(bool &success)
{
    success = false;
    bool r = false;
    if (IsOpened())
    {
        DWORD dwModemStatus;
        if (GetCommModemStatus(hComm, &dwModemStatus))
        {
            r = MS_DSR_ON & dwModemStatus;
            success = true;
        }
    }
    return r;
}

/**
 * @brief 获取RI
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetRI(bool &success)
{
    success = false;
    bool r = false;
    if (IsOpened())
    {
        DWORD dwModemStatus;
        if (GetCommModemStatus(hComm, &dwModemStatus))
        {
            r = MS_RING_ON & dwModemStatus;
            success = true;
        }
    }
    return r;
}

/**
 * @brief 获取CD
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetCD(bool &success)
{
    success = false;
    bool r = false;
    if (IsOpened())
    {
        DWORD dwModemStatus;
        if (GetCommModemStatus(hComm, &dwModemStatus))
        {
            r = MS_RLSD_ON & dwModemStatus;
            success = true;
        }
    }
    return r;
}

#else // for POSIX
/**
 * @brief 打开串口
 * 
 * @return long 
 */
long ceSerial::Open(void)
{
    struct serial_struct serinfo;
    struct termios settings;
    memset(&settings, 0, sizeof(settings));
    settings.c_iflag = 0;
    settings.c_oflag = 0;

    settings.c_cflag = CREAD | CLOCAL; // see termios.h for more information
    if (dsize == 5)
        settings.c_cflag |= CS5; // no change
    else if (dsize == 6)
        settings.c_cflag |= CS6;
    else if (dsize == 7)
        settings.c_cflag |= CS7;
    else
        settings.c_cflag |= CS8;

    if (stopbits == 2)
        settings.c_cflag |= CSTOPB;

    if (parity != 'N')
        settings.c_cflag |= PARENB;

    if (parity == 'O')
        settings.c_cflag |= PARODD;

    settings.c_lflag = 0;
    settings.c_cc[VMIN] = 1;
    settings.c_cc[VTIME] = 0;

    fd = open(port.c_str(), O_RDWR | O_NONBLOCK);
    if (fd == -1)
    {
        return -1;
    }

    if (!stdbaud)
    {
        // serial driver to interpret the value B38400 differently
        serinfo.reserved_char[0] = 0;
        if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
        {
            return -1;
        }
        serinfo.flags &= ~ASYNC_SPD_MASK;
        serinfo.flags |= ASYNC_SPD_CUST;
        serinfo.custom_divisor = (serinfo.baud_base + (baud / 2)) / baud;
        if (serinfo.custom_divisor < 1)
            serinfo.custom_divisor = 1;
        if (ioctl(fd, TIOCSSERIAL, &serinfo) < 0)
        {
            return -1;
        }
        if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
        {
            return -1;
        }
        if (serinfo.custom_divisor * baud != serinfo.baud_base)
        {
            /*
            warnx("actual baudrate is %d / %d = %f\n",
                serinfo.baud_base, serinfo.custom_divisor,
                (float)serinfo.baud_base / serinfo.custom_divisor);
            */
        }
        cfsetospeed(&settings, B38400);
        cfsetispeed(&settings, B38400);
    }
    else
    {
        cfsetospeed(&settings, baud);
        cfsetispeed(&settings, baud);
    }
    tcsetattr(fd, TCSANOW, &settings);
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    if (!stdbaud)
    {
        // driver to interpret B38400 as 38400 baud again
        ioctl(fd, TIOCGSERIAL, &serinfo);
        serinfo.flags &= ~ASYNC_SPD_MASK;
        ioctl(fd, TIOCSSERIAL, &serinfo);
    }
    return 0;
}

/**
 * @brief 关闭串口
 * 
 */
void ceSerial::Close()
{
    if (IsOpened())
        close(fd);
    fd = -1;
}

/**
 * @brief 查看串口是否打开
 * 
 * @return true 已经打开
 * @return false 没有打开
 */
bool ceSerial::IsOpened()
{
    if (fd == (-1))
        return false;
    else
        return true;
}

/**
 * @brief 设置波特率
 * 
 * @param baudrate 要设置的波特率参数
 */
void ceSerial::SetBaudRate(long baudrate)
{
    stdbaud = true;
    if (baudrate == 0)
        baud = B0;
    else if (baudrate == 50)
        baud = B50;
    else if (baudrate == 75)
        baud = B75;
    else if (baudrate == 110)
        baud = B110;
    else if (baudrate == 134)
        baud = B134;
    else if (baudrate == 150)
        baud = B150;
    else if (baudrate == 200)
        baud = B200;
    else if (baudrate == 300)
        baud = B300;
    else if (baudrate == 600)
        baud = B600;
    else if (baudrate == 1200)
        baud = B1200;
    else if (baudrate == 2400)
        baud = B2400;
    else if (baudrate == 4800)
        baud = B4800;
    else if (baudrate == 9600)
        baud = B9600;
    else if (baudrate == 19200)
        baud = B19200;
    else if (baudrate == 38400)
        baud = B38400;
    else if (baudrate == 57600)
        baud = B57600;
    else if (baudrate == 115200)
        baud = B115200;
    else if (baudrate == 230400)
        baud = B230400;
    else
    {
        baud = baudrate;
        stdbaud = false;
    }
}

/**
 * @brief 获取波特率情况
 * 
 * @return long 
 */
long ceSerial::GetBaudRate()
{
    return baud;
}

/**
 * @brief 接收一个字节
 * 
 * @param success 接收成功标志
 * @return char 接收到的字节
 */
char ceSerial::ReadChar(bool &success)
{
    success = false;
    if (!IsOpened())
    {
        return 0;
    }
    success = read(fd, &rxchar, 1) == 1;
    return rxchar;
}

/**
 * @brief 串口发送
 * 
 * @param data 要发送的数据
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::Write(char *data)
{
    if (!IsOpened())
    {
        return false;
    }
    long n = strlen(data);
    if (n < 0)
        n = 0;
    else if (n > 1024)
        n = 1024;
    return (write(fd, data, n) == n);
}

/**
 * @brief 串口发送数据的特定长度
 * 
 * @param data 发送数据
 * @param n 要发送的长度
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::Write(char *data, long n)
{
    if (!IsOpened())
    {
        return false;
    }
    if (n < 0)
        n = 0;
    else if (n > 1024)
        n = 1024;
    return (write(fd, data, n) == n);
}

/**
 * @brief 发送一个字节
 * 
 * @param ch 要发送的数据
 * @return true 发送成功
 * @return false 发送失败
 */
bool ceSerial::WriteChar(char ch)
{
    char s[2];
    s[0] = ch;
    s[1] = 0; // null terminated
    return Write(s);
}

/**
 * @brief 设置RTS
 * 
 * @param value 
 * @return true 
 * @return false 
 */
bool ceSerial::SetRTS(bool value)
{
    long RTS_flag = TIOCM_RTS;
    bool success = true;
    if (value)
    { // Set RTS pin
        if (ioctl(fd, TIOCMBIS, &RTS_flag) == -1)
            success = false;
    }
    else
    { // Clear RTS pin
        if (ioctl(fd, TIOCMBIC, &RTS_flag) == -1)
            success = false;
    }
    return success;
}

/**
 * @brief 设置DTR
 * 
 * @param value 
 * @return true 
 * @return false 
 */
bool ceSerial::SetDTR(bool value)
{
    long DTR_flag = TIOCM_DTR;
    bool success = true;
    if (value)
    { // Set DTR pin
        if (ioctl(fd, TIOCMBIS, &DTR_flag) == -1)
            success = false;
    }
    else
    { // Clear DTR pin
        if (ioctl(fd, TIOCMBIC, &DTR_flag) == -1)
            success = false;
    }
    return success;
}

/**
 * @brief 获取CTS
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetCTS(bool &success)
{
    success = true;
    long status;
    if (ioctl(fd, TIOCMGET, &status) == -1)
        success = false;
    return ((status & TIOCM_CTS) != 0);
}

/**
 * @brief 获取DSR
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetDSR(bool &success)
{
    success = true;
    long status;
    if (ioctl(fd, TIOCMGET, &status) == -1)
        success = false;
    return ((status & TIOCM_DSR) != 0);
}

/**
 * @brief 获取RI
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetRI(bool &success)
{
    success = true;
    long status;
    if (ioctl(fd, TIOCMGET, &status) == -1)
        success = false;
    return ((status & TIOCM_RI) != 0);
}

/**
 * @brief 获取CD
 * 
 * @param success 
 * @return true 
 * @return false 
 */
bool ceSerial::GetCD(bool &success)
{
    success = true;
    long status;
    if (ioctl(fd, TIOCMGET, &status) == -1)
        success = false;
    return ((status & TIOCM_CD) != 0);
}
#endif
//-----------------------------------------------------------------------------
